﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
using System.Diagnostics;
using MongoDB.Bson;

namespace DFlowCore
{
    public class BsonUtils
    {

        public static BsonDocument Decode(byte[] b) { return BsonDocument.ReadFrom(b); }

        public static byte[] Encode(BsonDocument msg)
        {
            byte[] b;
            MemoryStream ms = new MemoryStream();
            MongoDB.Bson.IO.BsonWriter bw = MongoDB.Bson.IO.BsonBinaryWriter.Create(ms);
            msg.WriteTo(bw);
            bw.Flush();
            ms.Seek(0, SeekOrigin.Begin);
            using (BinaryReader br = new BinaryReader(ms))
            {
                b = br.ReadBytes((int)ms.Length);
            }
            return b;
        }

        public static byte[] Encode(BsonValue msg)
        {
            byte[] b;
            MemoryStream ms = new MemoryStream();
            MongoDB.Bson.IO.BsonWriter bw = MongoDB.Bson.IO.BsonBinaryWriter.Create(ms);
            msg.WriteTo(bw);
            bw.Flush();
            ms.Seek(0, SeekOrigin.Begin);
            using (BinaryReader br = new BinaryReader(ms))
            {
                b = br.ReadBytes((int)ms.Length);
            }
            return b;
        }

        [AttributeUsage(AttributeTargets.Class)]
        public class BsonCodec : System.Attribute
        {
            public string name;
            public Type type;
            public BsonCodec(string _s, Type _t) { type = _t; name = _s; }
        }


        [BsonCodec("int", typeof(int))]
        static public class Int2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                return new BsonInt32((int)o);
            }
            public static object Decode(BsonValue bv)
            {
                return bv.AsInt32;
            }
        }

        [BsonCodec("int", typeof(short))]
        static public class Short2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                return new BsonInt32((int)o);
            }
            public static object Decode(BsonValue bv)
            {
                return bv.AsInt32;
            }
        }


        [BsonCodec("bool", typeof(bool))]
        static public class Bool2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                return ((bool)o)?BsonBoolean.True:BsonBoolean.False;
            }
            public static object Decode(BsonValue bv)
            {
                return bv.AsBoolean;
            }
        }


        [BsonCodec("int", typeof(byte))]
        static public class Byte2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                return new BsonInt32((int)o);
            }
            public static object Decode(BsonValue bv)
            {
                return bv.AsInt32;
            }
        }

        [BsonCodec("string", typeof(string))]
        static public class String2BsonManager
        {
            public static BsonValue Encode(object o)
            {

                return (o == null) ? BsonString.Empty : (new BsonString((string)o));
            }
            public static object Decode(BsonValue bv)
            {
                return bv.AsString;
            }
        }

        [BsonCodec("bsondocument", typeof(BsonDocument))]
        static public class Doc2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                return ((BsonDocument)o);
            }
            public static object Decode(BsonValue bv)
            {
                return bv.AsBsonDocument;
            }
        }



        [BsonCodec("float", typeof(float))]
        static public class Float2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                return new BsonDouble((double)(float)o);
            }
            public static object Decode(BsonValue bv)
            {
                return (float)bv.AsDouble;
            }
        }



        [BsonCodec("float", typeof(double))]
        static public class Double2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                return new BsonDouble((double)o);
            }
            public static object Decode(BsonValue bv)
            {
                return bv.AsDouble;
            }
        }



        [BsonCodec("vector2f", typeof(Mathf.Vector2))]
        static public class Vector2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                BsonDocument bd = new BsonDocument();
                Mathf.Vector2 v = (Mathf.Vector2)o;
                bd.Add("x", v.x); bd.Add("y", v.y);
                return bd;
            }
            public static object Decode(BsonValue bv)
            {
                BsonDocument bd = bv.AsBsonDocument;
                return new Mathf.Vector2((float)bd["x"].AsDouble, (float)bd["y"].AsDouble);
            }
        }




        [BsonCodec("vector3f", typeof(Mathf.Vector3))]
        static public class Vector3BsonManager
        {
            public static BsonValue Encode(object o)
            {
                BsonDocument bd = new BsonDocument();
                Mathf.Vector3 v = (Mathf.Vector3)o;
                bd.Add("x", v.x); bd.Add("y", v.y);bd.Add("z",v.z);
                return bd;
            }
            public static object Decode(BsonValue bv)
            {
                BsonDocument bd = bv.AsBsonDocument;
                return new Mathf.Vector3((float)bd["x"].AsDouble, (float)bd["y"].AsDouble, (float)bd["z"].AsDouble);
            }
        }


        [BsonCodec("vector4f", typeof(Mathf.Vector4))]
        static public class Vector4BsonManager
        {
            public static BsonValue Encode(object o)
            {
                BsonDocument bd = new BsonDocument();
                Mathf.Vector4 v = (Mathf.Vector4)o;
                bd.Add("x", v.x); bd.Add("y", v.y); bd.Add("z", v.z); bd.Add("w",v.w);
                return bd;
            }
            public static object Decode(BsonValue bv)
            {
                BsonDocument bd = bv.AsBsonDocument;
                return new Mathf.Vector4(
                                               (float)bd["x"].AsDouble, 
                                               (float)bd["y"].AsDouble,
                                               (float)bd["z"].AsDouble,
                                               (float)bd["w"].AsDouble
                                               );
            }
        }





        [BsonCodec("vector4f", typeof(Mathf.Quaternion))]
        static public class Quaternion2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                BsonDocument bd = new BsonDocument();
                Mathf.Quaternion v = (Mathf.Quaternion)o;
                bd.Add("x", v.x); bd.Add("y", v.y); bd.Add("z", v.z); bd.Add("w", v.w);
                return bd;
            }
            public static object Decode(BsonValue bv)
            {
                BsonDocument bd = bv.AsBsonDocument;
                return new Mathf.Quaternion(
                                               (float)bd["x"].AsDouble,
                                               (float)bd["y"].AsDouble,
                                               (float)bd["z"].AsDouble,
                                               (float)bd["w"].AsDouble
                                               );
            }
        }





        [BsonCodec("image", typeof(System.Drawing.Image))]
        static public class Image2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                byte[] b;
                using (MemoryStream ms = new MemoryStream())
                {
                    ((System.Drawing.Bitmap)o).Save(
                        ms,
                        System.Drawing.Imaging.ImageFormat.Png
                        );
                    ms.Seek(0, SeekOrigin.Begin);
                    using (BinaryReader br = new BinaryReader(ms))
                    {
                        b = br.ReadBytes((int)ms.Length);
                    }
                }

                return new BsonBinaryData(b);
            }
            public static object Decode(BsonValue bv)
            {
                System.Drawing.Bitmap bmp=null;
                using (MemoryStream ms = new MemoryStream(bv.AsByteArray))
                {
                    bmp=(System.Drawing.Bitmap)System.Drawing.Bitmap.FromStream(ms);
                }
                return bmp;
            }
        }




        [BsonCodec("color", typeof(System.Drawing.Color))]
        static public class Color2BsonManager
        {
            public static BsonValue Encode(object o)
            {
                System.Drawing.Color co = ( System.Drawing.Color)o;
                return new BsonArray(new BsonValue[] { new BsonDouble(co.R / 255.0), new BsonDouble(co.G / 255.0), new BsonDouble(co.B / 255.0), new BsonDouble(co.A / 255.0) });
            }
            public static object Decode(BsonValue o)
            {
                return System.Drawing.Color.FromArgb(((int)(o.AsBsonArray[3].AsDouble * 255)), ((int)(o.AsBsonArray[0].AsDouble * 255)), ((int)(o.AsBsonArray[1].AsDouble * 255)), ((int)(o.AsBsonArray[2].AsDouble * 255)));
            }
        }




        public static class BsonCodecs
        {
            static private Dictionary<Type, DFlow.Pair<string,DFlow.Pair<Func<BsonValue,object>,Func<object,BsonValue>>>> _codecs=null;

            static public Dictionary<Type, DFlow.Pair<string, DFlow.Pair<Func<BsonValue, object>, Func<object, BsonValue>>>> codecs
            {
                get
                {
                    if (_codecs == null)
                    {
                        _codecs = new Dictionary<Type, DFlow.Pair<string, DFlow.Pair<Func<BsonValue, object>, Func<object, BsonValue>>>>();
                        UpdateBsonCodecs();
                    }
                    return _codecs;
                }
            }

            static public void RegisterAssembly(System.Reflection.Assembly a)
            {
                //System.Diagnostics.Debug.Print(a.FullName);_
                foreach (System.Type t in a.GetTypes())
                {
                        foreach (object to in t.GetCustomAttributes(typeof(BsonCodec), true))
                        {
                            BsonCodec bc = to as BsonCodec;
                            System.Reflection.MethodInfo micoder=t.GetMethod("Encode");
                            System.Reflection.MethodInfo midecoder=t.GetMethod("Decode");
                            //System.Diagnostics.Debug.Print("     " + mi.Name);
                            if (!codecs.ContainsKey(bc.type))
                            {
                                codecs.Add(bc.type,new DFlow.Pair<string,DFlow.Pair<Func<BsonValue,object>,Func<object,BsonValue>>>(
                                    bc.name,new DFlow.Pair<Func<BsonValue,object>,Func<object,BsonValue>>(
                                        (bv)=>(midecoder.Invoke(null,new object[]{bv})),
                                        (o)=>((BsonValue)micoder.Invoke(null,new object[]{o}))
                                        )
                                    ));

                            }
                            else {
                                System.Diagnostics.Debug.Print("Bson CODEC already found for "+bc.type.FullName);
                            }
                        }
                    
                }

                //CloseTransitively();
            }

            static public void UpdateBsonCodecs()
            {
                codecs.Clear();
                foreach (System.Reflection.Assembly a in System.AppDomain.CurrentDomain.GetAssemblies())
                {
                    BsonCodecs.RegisterAssembly(a);
                }
            //    CloseTransitively();
            }


        }








        public static string TranslateTypeInfo(System.Reflection.FieldInfo fi)
        {
            foreach (Object o in fi.GetCustomAttributes(typeof(DFlow.ExplicitTypeInfoAttribute), false))
            {
                return (o as DFlow.ExplicitTypeInfoAttribute).type;
            }
            return TranslateTypeInfo(fi.FieldType);
        }


        public static string TranslateTypeInfo(System.Type t)
        {

            foreach (Object o in t.GetCustomAttributes(typeof(DFlow.ExplicitTypeInfoAttribute), false))
            {
                return (o as DFlow.ExplicitTypeInfoAttribute).type;
            }

            if (t.IsArray)
            {
                return "array<" + TranslateTypeInfo(t.GetElementType()) + ">";
            }
            if (t.IsEnum)
            {
                return "int";
            }

            if (BsonCodecs.codecs.ContainsKey(t))
            {
                return BsonCodecs.codecs[t].first;
            }



            if (t.IsGenericType)
            {

                if (t.GetGenericTypeDefinition() == typeof(System.Collections.Generic.List<int>).GetGenericTypeDefinition())
                {
                    return "list<" + TranslateTypeInfo(t.GetGenericArguments()[0]) + ">";
                }

                if (t.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IEnumerable<int>).GetGenericTypeDefinition())
                {
                    return "list<" + TranslateTypeInfo(t.GetGenericArguments()[0]) + ">";
                }


                if (t.GetGenericTypeDefinition() == typeof(DFlowCore.ArrayView<int>).GetGenericTypeDefinition())
                {
                    return "array<" + TranslateTypeInfo(t.GetGenericArguments()[0]) + ">";
                }
            }


            // TODO : TO MEMORIZE AS COMPUTED CONVERTERS
            foreach(var xt in BsonCodecs.codecs ) {
                Func<object,object,object> cv=DFlow.Converters.GetConverter(t, xt.Key, true);
                if (cv!=null)
                {
                    return xt.Value.first;
                }
            }


            if ((t.IsClass)||(t.IsLayoutSequential))
            {
                return "bsondocument";  // by default we reflect each class as a bsondocuments of its public  fields.
            }

            throw new System.Exception("Unknown type cannot be described in portable fashion :" + t.Name);
        }



        public static BsonValue BsonEncode(object o, System.Type t)
        {
            if ((t==null)||(t == typeof(System.Object))) { t = o.GetType(); }
            if (t.IsArray)
            {
                System.Type te = t.GetElementType();
                if (te.IsPrimitive)
                {
                    if ((te.Name) == typeof(System.Int32).Name)
                    {
                        if (o == null) return new BsonArray();
                        return new BsonArray((System.Collections.Generic.IEnumerable<BsonValue>)
                             System.Array.ConvertAll<System.Int32, BsonValue>(((System.Int32 [])o), 
                                                                       (x => new BsonInt32(x)))
                                                                       );
                    }
                    if ((te.Name) == typeof(System.Int64).Name)
                    {
                        if (o == null) return new BsonArray();
                        return new BsonArray((System.Collections.Generic.IEnumerable<BsonValue>)
                             System.Array.ConvertAll<System.Int64, BsonValue>(((System.Int64[])o),
                                                                       (x => new BsonInt64(x)))
                                                                       );
                    }
                    if ((te.Name) == typeof(double).Name)
                    {
                        if (o == null) return new BsonArray();
                        return new BsonArray((System.Collections.Generic.IEnumerable<BsonValue>)
                             System.Array.ConvertAll<System.Double, BsonValue>(((System.Double[])o),
                                                                       (x => new BsonDouble(x)))
                                                                       );
                    }
                    if ((te.Name) == typeof(float).Name)
                    {
                        if (o == null) return new BsonArray();
                        return new BsonArray((System.Collections.Generic.IEnumerable<BsonValue>)
                             System.Array.ConvertAll<System.Single, BsonValue>(((System.Single[])o),
                                                                       (x => new BsonDouble(x)))
                                                                       );

                    }
                }


                
                BsonArray ar = new BsonArray();
                if (t.GetArrayRank()!=1) {
                    throw new System.Exception("Only rank 1 array supported for the moment");
                }

                {
                    System.Type ot = o.GetType();
                    System.Reflection.MethodInfo mi2 = ot.GetMethod("GetEnumerator");
                    object enumerator = mi2.Invoke(o, null);
                    System.Type et = enumerator.GetType();
                    System.Reflection.MethodInfo getnext = et.GetMethod("MoveNext");
                    System.Reflection.PropertyInfo current = et.GetProperty("Current");

                    BsonArray res = new BsonArray();

                    while ((bool)getnext.Invoke(enumerator, null))
                    {
                        object x = current.GetValue(enumerator, null);
                        if (x == null)
                        {
                            res.Add(BsonNull.Value);
                        }
                        else
                        {
                            res.Add(BsonEncode(x, x.GetType()));                 
                        }
                    }

                    return res;
                }

                throw new System.Exception("se");
            }


            if (t.IsEnum)
            {
                return new BsonInt32((System.Int32)o);
            }

            if (BsonCodecs.codecs.ContainsKey(t))
            {
                return BsonCodecs.codecs[t].second.second(o);
            }


            if (t.IsGenericType)
            {

                if ((t.GetGenericTypeDefinition() == typeof(System.Collections.Generic.List<int>).GetGenericTypeDefinition())
                    ||(t.GetMethod("GetEnumerator")!=null))
                {
                    System.Type ot = o.GetType();
                    System.Reflection.MethodInfo mi2 = ot.GetMethod("GetEnumerator");
                    object enumerator=mi2.Invoke(o,null);
                    System.Type et = enumerator.GetType();
                    System.Reflection.MethodInfo getnext= et.GetMethod("MoveNext");
                    System.Reflection.PropertyInfo current = et.GetProperty("Current");

                    BsonArray res=new BsonArray();
                    
                    while((bool)getnext.Invoke(enumerator,null)) {
                        object x=current.GetValue(enumerator,null);
                        if (x==null) {
                            res.Add(BsonNull.Value);
                        }
                        else {
                            res.Add(BsonEncode(x,x.GetType()));
                            //res.Add(BsonEncode(x, t.GetGenericArguments()[0]/* null*/));
                        }
                    }

                    return res;

                }
            }





            // TODO : TO MEMORIZE AS COMPUTED CONVERTERS
            foreach (var xt in BsonCodecs.codecs)
            {
                Func<object, object, object> cv = DFlow.Converters.GetConverter(t, xt.Key,true);
                if (cv != null)
                {
                    return xt.Value.second.second(cv(o,null));
                }
            }






            if (t.IsClass||t.IsLayoutSequential)
            {
                try
                {
                    if (t.GetMethod("ToBson") != null)
                    {
                        return (BsonValue) t.GetMethod("ToBson").Invoke(o,null);
                    }
                }
                catch { }


                BsonDocument bd = new BsonDocument();
                //bd.Add("__type", t.FullName);
                foreach (System.Reflection.FieldInfo fi in t.GetFields())
                {                    
                    bd.Add(fi.Name, (fi.GetValue(o)!=null)?(BsonEncode(fi.GetValue(o), fi.FieldType)):null);
                }
                foreach (System.Reflection.CustomAttributeData cad in t.GetCustomAttributesData())
                {
                    foreach (System.Reflection.CustomAttributeNamedArgument na in cad.NamedArguments)
                    {
                        //System.Reflection.Anonymous
                        bd.Add(na.MemberInfo.Name, BsonEncode(na.TypedValue.Value, na.TypedValue.ArgumentType));
                    }
                }
                foreach (System.Reflection.PropertyInfo pi in t.GetProperties())
                {
                    
                    //for (pis[0].
                    if (pi.CanWrite) // non writable propeties are likely to be function results...
                    {
                        System.Reflection.ParameterInfo[] pis = pi.GetIndexParameters();
                        if (pis.Length == 0)
                        {
                            try
                            {

                                //System.Diagnostics.Debug.Print(pi.Name);
                                bd.Add(pi.Name, BsonEncode(pi.GetValue(o, null), pi.PropertyType));
                            }
                            catch (System.Reflection.TargetInvocationException se)
                            {
                                DFlowCore.Log.Warning(se.ToString());
                            }
                        }
                        else
                        {
                            BsonArray ar = new BsonArray();
                            int ci = 0;
                            while (true)
                            {
                                try
                                {
                                    ar.Add(BsonEncode(pi.GetValue(o, new object[] { ci }), pi.PropertyType));
                                    ci++;
                                }
                                catch (System.Reflection.TargetInvocationException )
                                {
                                    break;
                                }
                                catch (System.IndexOutOfRangeException)
                                {
                                    break;
                                }
                            }
                            bd.Add(pi.Name, ar);
                        }
                    }
                }

                return bd;
            }

            throw new System.Exception("Unknown type cannot be described in portable fashion :" + t.Name);


        }




        public static object UpdateObject(object o, BsonDocument d)
        {
            System.Type t=o.GetType();
            foreach (BsonElement be in d)
            {
                System.Reflection.FieldInfo fi = t.GetField(be.Name);
                if (fi != null)
                {
                    fi.SetValue(o, DFlowCore.BsonUtils.BsonDecode(be.Value, fi.FieldType));
                    foreach (DFlow.Attribute va in fi.FieldType.GetCustomAttributes(typeof(DFlow.Attribute),true))
                    {
                        va.AfterUpdateValue(fi, o);
                    }
                }
                else
                {
                    System.Diagnostics.Debug.Print("No such attribute "+ be.Name);
                }
            }
            return o;
        }


        public static object BsonDecode(BsonValue o, System.Type t)
        {

            if (t.IsArray)
            {
                // System.Array sa = o as System.Array; ;
                if (!o.IsBsonArray) { throw new System.Exception("BSON Data do not match"); }
                object[] ta = new object[o.AsBsonArray.Count];
                //for (int i = 0; i < o.Length; i++) { ta[i] = }
                throw new System.Exception("Not yet implemented");
                //System.Array a=new System.Array();

                //return (object)ta;
                //return new BsonArray((System.Collections.Generic.IEnumerable<BsonValue>)
                //                     System.Array.ConvertAll<object, BsonValue>(ta, (x => BsonEncode(x, t.GetElementType())))
                //                     );
            }

            if (t.IsEnum)
            {
                return Enum.ToObject(t, o.AsInt32);
            }

            if (BsonCodecs.codecs.ContainsKey(t))
            {
                return BsonCodecs.codecs[t].second.first(o);
            }


            if (t.IsGenericType)
            {
                if (t.GetGenericTypeDefinition() == typeof(List<int>).GetGenericTypeDefinition())
                {
                    object r = t.InvokeMember(null, System.Reflection.BindingFlags.CreateInstance, null, null, null);
                    System.Reflection.MethodInfo madd = t.GetMethod("Add");
                    System.Type st = t.GetGenericArguments()[0];
                    foreach (BsonValue a in o.AsBsonArray)
                    {
                        madd.Invoke(r,new object[]{BsonDecode(a, st)});
                    }
                    return r;
                }

            }

            // TODO : TO MEMORIZE AS COMPUTED CONVERTERS
            foreach (var xt in BsonCodecs.codecs)
            {
                Func<object, object, object> cv = DFlow.Converters.GetConverter( xt.Key,t,true);
                if (cv != null)
                {
                    return cv(xt.Value.second.first(o),null);
                }
            }


            if (t.IsClass || t.IsLayoutSequential)
            {
                object res = t.InvokeMember(null, System.Reflection.BindingFlags.CreateInstance, null, null, null);
                foreach (System.Reflection.FieldInfo fi in t.GetFields())
                {
                    if (o.AsBsonDocument.Contains(fi.Name))
                    {
                        fi.SetValue(res, BsonDecode(o.AsBsonDocument[fi.Name], fi.FieldType));
                    }
                }
                foreach (System.Reflection.PropertyInfo pi in t.GetProperties())
                {
                    if (pi.CanWrite) // non writable propeties are likely to be function results...
                    {
                        System.Reflection.ParameterInfo[] pis = pi.GetIndexParameters();
                        if (pis.Length == 0)
                        {
                            try
                            {

                                if (o.AsBsonDocument.Contains(pi.Name))
                                {
                                    pi.SetValue(res, BsonDecode(o.AsBsonDocument[pi.Name], pi.PropertyType), null);
                                }
                            }
                            catch (System.Reflection.TargetInvocationException se)
                            {
                                DFlowCore.Log.Warning(se.ToString());
                            }
                        }
                        else
                        {
                            BsonArray ar = new BsonArray();
                            int ci = 0;
                            while (true)
                            {
                                try
                                {
                                    if (o.AsBsonDocument.Contains(pi.Name))
                                    {
                                        pi.SetValue(res, BsonDecode(o.AsBsonDocument[pi.Name].AsBsonArray[ci], pi.PropertyType), new object[] { ci });
                                    }
                                    ci++;
                                }
                                catch (System.Reflection.TargetInvocationException )
                                {
                                    break;
                                }
                                catch (System.IndexOutOfRangeException )
                                {
                                    break;
                                }
                            }                            
                        }
                    }
                    return res;
                }

            }

            throw new System.Exception("Type not yet supported" + t.Name);

            
        }




    }


}
